﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Netfraction.Network.Protocol.BASE.Factory
{
    public class FactorySTA : ProtocolMessageFactory
    {
        // Note that this enum is sequential in numbering and entirely unrelated to the error codes.
        public enum ParameterEnum : int
        {
            Severity = 0,
            Code = 1, 
            Description = 2,
            FourCC = 3,
            TimeLeft = 4,
            Token = 5,
            Protocol = 6,
            FieldMissing = 7,
            FieldBad = 8,
            IPAddressV4 = 9,
            IPAddressV6 = 10,
            SessionID = 11,
            InitiatorSID = 12
        }

        // StatusSeverity and StatusErrorCode are identical to the HandlerSTA enums. Probably a bit of a waste?
        public enum StatusSeverity : int
        {
            Success = 0,
            RecoverableError = 1,
            FatalError = 2
        }

        // Errors x0 are generic, with the following xy errors being more specific, relating to x
        // "Most error codes don't have parameters and only make sense in C and I types."
        public enum StatusErrorCode : int
        {
            Generic = 0,
            GenericHubError = 10,
            HubFull = 11,
            HubDisabled = 12,
            GenericLoginError = 20,
            NickInvalid = 21,
            NickTaken = 22,
            InvalidPassword = 23,
            ClientIDTaken = 24,
            CommandDenied = 25,
            RegisteredUsersOnly = 26,
            InvalidPrivateID = 27,
            GenericKickBanDisconnect = 30,
            PermanentlyBanned = 31,
            TemporarilyBanned = 32,
            ProtocolError = 40,
            TransferProtocolUnsupported = 41,
            DirectConnectionFailed = 42,
            RequiredINFFieldMissingOrBad = 43,
            InvalidState = 44,
            RequiredFeatureMissing = 45,
            InvalidIP = 46,
            NoCommonHashFeatureWithHub = 47,
            C2COrFileTransferError = 50,
            FileNotAvailable = 51,
            FilePartNotAvailable = 52,
            SlotsFull = 53,
            NoCommonHashFeatureWithClient = 54
        }

        public override ProtocolMessage Create(ProtocolMessage.MessageTypeEnum messageType, Dictionary<int, object> parameters)
        {
            ProtocolMessage proto = new ProtocolMessage(messageType, ProtocolFeatureBASE.FeatureActions.ActionSTA);

            if (messageType == ProtocolMessage.MessageTypeEnum.Direct)
            {
                proto.Parameters.Add(new ProtocolParameter(parameters[(int)ParameterEnum.SessionID].ToString()));
                proto.Parameters.Add(new ProtocolParameter(parameters[(int)ParameterEnum.InitiatorSID].ToString()));
            }
            // Combine the severity and code into first parameter.
            int severity = (int)parameters[(int)ParameterEnum.Severity];
            int code = (int)parameters[(int)ParameterEnum.Code];

            // Param is in the form xyy, so the * 100 trick works. Use "d3" format to ensure a 3 digit string
            string codeParam = (severity * 100 + code).ToString("d3");

            proto.Parameters.Add(new ProtocolParameter(codeParam));

            // If the parameters define a custom error message, use that. Otherwise just send a general description for the error code.
            if (!parameters.ContainsKey((int)ParameterEnum.Description))
            {
                proto.Parameters.Add(new ProtocolParameter(GetErrorMessageForCode((StatusErrorCode)code)));
            }
            else
            {
                proto.Parameters.Add(new ProtocolParameter(parameters[(int)ParameterEnum.Description].ToString()));
            }

            foreach (ParameterEnum en in parameters.Keys)
            {
                // This loop is just for flags, these three are already taken care of above.
                if (en != ParameterEnum.Code && en != ParameterEnum.Description && en != ParameterEnum.Severity)
                {
                    proto.Parameters.Add(new ProtocolParameter(GetParameterCodeForParameter((ParameterEnum)en), parameters[(int)en].ToString()));
                }
            }

            return proto;
        }

        private string GetParameterCodeForParameter(ParameterEnum param)
        {
            switch (param)
            {
                case ParameterEnum.Severity:  // bit of a problem because Severity and Code are returned as a combined field
                    return "Severity";
                case ParameterEnum.Code:
                    return "Code";
                case ParameterEnum.Description:
                    return "Description";
                case ParameterEnum.FourCC:
                    return "FC";
                case ParameterEnum.TimeLeft:
                    return "TL";
                case ParameterEnum.Token:
                    return "TO";
                case ParameterEnum.Protocol:
                    return "PR";
                case ParameterEnum.FieldMissing:
                    return "FM";
                case ParameterEnum.FieldBad:
                    return "FB";
                case ParameterEnum.IPAddressV4:
                    return "I4";
                case ParameterEnum.IPAddressV6:
                    return "I6";
                default:
                    return "";
            }
        }

        private string GetErrorMessageForCode(StatusErrorCode code)
        {
            switch (code)
            {
                case StatusErrorCode.Generic:
                    return "A generic error has occured.";
                case StatusErrorCode.GenericHubError:
                    return "A generic hub error has occured.";
                case StatusErrorCode.HubFull:
                    return "The hub is currently full.";
                case StatusErrorCode.HubDisabled:
                    return "The hub is currently disabled.";
                case StatusErrorCode.GenericLoginError:
                    return "An unknown login error has occured.";
                case StatusErrorCode.NickInvalid:
                    return "The nickname supplied is invalid.";
                case StatusErrorCode.NickTaken:
                    return "The nickname supplied is already in use.";
                case StatusErrorCode.InvalidPassword:
                    return "The password supplied is invalid.";
                case StatusErrorCode.ClientIDTaken:
                    return "Your client ID is already in use.";
                case StatusErrorCode.CommandDenied:
                    return "Access denied.";
                case StatusErrorCode.RegisteredUsersOnly:
                    return "You must be a registered user to do that.";
                case StatusErrorCode.InvalidPrivateID:
                    return "Your private ID is invalid.";
                case StatusErrorCode.GenericKickBanDisconnect:
                    return "You have been kicked, banned or disconnected.";
                case StatusErrorCode.PermanentlyBanned:
                    return "You have been permanently banned.";
                case StatusErrorCode.TemporarilyBanned:
                    return "You have been temporarily banned.";
                case StatusErrorCode.ProtocolError:
                    return "A generic protocol error has occured.";
                case StatusErrorCode.TransferProtocolUnsupported:
                    return "The transfer protocol is unsupported.";
                case StatusErrorCode.DirectConnectionFailed:
                    return "A direct connection has failed.";
                case StatusErrorCode.RequiredINFFieldMissingOrBad:
                    return "A required information field is missing or bad.";
                case StatusErrorCode.InvalidState:
                    return "Message cannot be sent in the current state.";
                case StatusErrorCode.RequiredFeatureMissing:
                    return "A required feature is missing.";
                case StatusErrorCode.InvalidIP:
                    return "The supplied IP address is invalid.";
                case StatusErrorCode.NoCommonHashFeatureWithHub:
                    return "The client and hub do not support a common hash algorithm.";
                case StatusErrorCode.C2COrFileTransferError:
                    return "A client-client or file transfer error has occured.";
                case StatusErrorCode.FileNotAvailable:
                    return "The requested file is not available.";
                case StatusErrorCode.FilePartNotAvailable:
                    return "The requested file piece is not available.";
                case StatusErrorCode.SlotsFull:
                    return "All slots are currently full.";
                case StatusErrorCode.NoCommonHashFeatureWithClient:
                    return "The clients do not support a common hash algorithm.";
                default:
                    return "";
            }
        }
    }
}
